package com.cb2.algorithm.leetcode;

import java.util.HashMap;
import java.util.Map;

/**
 * <a href='https://leetcode.cn/problems/lfu-cache/description/'>LFU 缓存(LFU Cache)</a>
 * <p>请你为<a href='https://baike.baidu.com/item/%E7%BC%93%E5%AD%98%E7%AE%97%E6%B3%95'>最不经常使用（LFU）</a> 缓存算法设计并实现数据结构。</p>
 * <p>
 * 实现 LFUCache 类：
 *     <ul>
 *         <li>LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象</li>
 *         <li>int get(int key) - 如果键 key 存在于缓存中，则获取键的值，否则返回 -1 。</li>
 *         <li>void put(int key, int value) - 如果键 key 已存在，则变更其值；如果键不存在，请插入键值对。当缓存达到其容量 capacity 时，则应该在插入新项之前，移除最不经常使用的项。在此问题中，当存在平局（即两个或更多个键具有相同使用频率）时，应该去除 最久未使用 的键。</li>
 *     </ul>
 *     为了确定最不常使用的键，可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。<br>
 *     当一个键首次插入到缓存中时，它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作，使用计数器的值将会递增。
 * </p>
 * <p>函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。</p>
 *
 * <p>
 * <b>示例</b>
 * <pre>
 * 示例：
 *      输入：
 *          ["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
 *          [[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
 *      输出：
 *          [null, null, null, 1, null, -1, 3, null, -1, 3, 4]
 *      解释：
 *          // cnt(x) = 键 x 的使用计数
 *          // cache=[] 将显示最后一次使用的顺序（最左边的元素是最近的）
 *          LFUCache lfu = new LFUCache(2);
 *          lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
 *          lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
 *          lfu.get(1);      // 返回 1
 *                           // cache=[1,2], cnt(2)=1, cnt(1)=2
 *          lfu.put(3, 3);   // 去除键 2 ，因为 cnt(2)=1 ，使用计数最小
 *                           // cache=[3,1], cnt(3)=1, cnt(1)=2
 *          lfu.get(2);      // 返回 -1（未找到）
 *          lfu.get(3);      // 返回 3
 *                           // cache=[3,1], cnt(3)=2, cnt(1)=2
 *          lfu.put(4, 4);   // 去除键 1 ，1 和 3 的 cnt 相同，但 1 最久未使用
 *                           // cache=[4,3], cnt(4)=1, cnt(3)=2
 *          lfu.get(1);      // 返回 -1（未找到）
 *          lfu.get(3);      // 返回 3
 *                           // cache=[3,4], cnt(4)=1, cnt(3)=3
 *          lfu.get(4);      // 返回 4
 *                           // cache=[3,4], cnt(4)=2, cnt(3)=3
 * </pre>
 * </p>
 * <p>
 * <b>提示：</b>
 *     <ul>
 *          <li>1 <= capacity <= 10^4</li>
 *          <li>0 <= key <= 10^5</li>
 *          <li>0 <= value <= 10^9</li>
 *          <li>最多调用 2 * 10^5 次 get 和 put 方法</li>
 *     </ul>
 * </p>
 *
 * @author c2b
 * @see LC0146LRUCache_M LRU 缓存(LRU Cache)
 * @see LC0460LFUCache_H LFU 缓存(LFU Cache)
 * @since 2023/12/12 15:23
 */
public class LC0460LFUCache_H {

    static class LFUCache {

        Map<Integer, Node> cache;
        /**
         * maxFreqLinkedList - 最大频次：从左往右频次越来越小
         */
        DoublyLinkedList maxFreqLinkedList;
        /**
         * maxFreqLinkedList - 最小频次：从左往右频次越来越小
         */
        DoublyLinkedList minFreqLinkedList;

        int capacity;

        public LFUCache(int capacity) {
            if (capacity <= 0) {
                throw new IllegalArgumentException("Illegal initial capacity: " + capacity);
            }
            cache = new HashMap<>(capacity);
            maxFreqLinkedList = new DoublyLinkedList();
            minFreqLinkedList = new DoublyLinkedList();
            maxFreqLinkedList.next = minFreqLinkedList;
            minFreqLinkedList.prev = maxFreqLinkedList;
            this.capacity = capacity;
        }

        public int get(int key) {
            Node node = cache.get(key);
            if (node == null) {
                return -1;
            }
            // 将当前节点移动到(freq+1)的节点上
            setOrIncrNodeFreq(node, true);
            return node.value;
        }

        public void put(int key, int value) {
            Node node = cache.get(key);
            if (node == null) {
                // 如果本次添加超出容量，移除频次最低且最久未访问的节点
                if (cache.size() == capacity) {
                    DoublyLinkedList targetLinkedList = minFreqLinkedList.prev;
                    cache.remove(targetLinkedList.tail.prev.key);
                    targetLinkedList.removeNode(targetLinkedList.tail.prev);
                    removeLinkedListIfNecessary(targetLinkedList);
                }
                // 创建新节点，并将频次设置为1
                node = new Node(key, value);
                setOrIncrNodeFreq(node, false);
                cache.put(key, node);
            } else {
                // 更新值，并使频次+1
                node.value = value;
                setOrIncrNodeFreq(node, true);
            }
        }

        /**
         * 设置或自增节点拼接
         *
         * @param node       目标节点
         * @param isIncrFreq true：增加频次。false：设置频次
         */
        private void setOrIncrNodeFreq(Node node, boolean isIncrFreq) {
            // 从原链表中移除
            DoublyLinkedList sourceLinkedList;
            if (isIncrFreq) {
                sourceLinkedList = node.linkedList;
                sourceLinkedList.removeNode(node);
            } else {
                sourceLinkedList = minFreqLinkedList;
            }
            // 找到目标链表：如果不存在则需要创建。将当前节点添加到链表中
            DoublyLinkedList targetLinkedList;
            if (sourceLinkedList.prev != maxFreqLinkedList && sourceLinkedList.prev.freq == sourceLinkedList.freq + 1) {
                targetLinkedList = sourceLinkedList.prev;
            } else {
                targetLinkedList = createLinkedListBeforeSource(sourceLinkedList);
            }
            targetLinkedList.addNodeToHead(node);
            // 如果原链表中无节点，移除原链表
            if (isIncrFreq) {
                removeLinkedListIfNecessary(sourceLinkedList);
            }
        }

        private DoublyLinkedList createLinkedListBeforeSource(DoublyLinkedList sourceDoublyLinkedList) {
            int freq;
            if (sourceDoublyLinkedList == minFreqLinkedList) {
                freq = 1;
            } else {
                freq = sourceDoublyLinkedList.freq + 1;
            }
            DoublyLinkedList newDoublyLinkedList = new DoublyLinkedList(freq);
            newDoublyLinkedList.prev = sourceDoublyLinkedList.prev;
            newDoublyLinkedList.next = sourceDoublyLinkedList;
            sourceDoublyLinkedList.prev.next = newDoublyLinkedList;
            sourceDoublyLinkedList.prev = newDoublyLinkedList;
            return newDoublyLinkedList;
        }

        private void removeLinkedListIfNecessary(DoublyLinkedList targetLinkedList) {
            if (targetLinkedList.head.next == targetLinkedList.tail) {
                targetLinkedList.prev.next = targetLinkedList.next;
                targetLinkedList.next.prev = targetLinkedList.prev;
            }
        }

        static class DoublyLinkedList {
            private DoublyLinkedList prev;
            private DoublyLinkedList next;
            /**
             * 双向链表的虚拟头节点：头节点为最近访问的节点
             */
            private Node head;
            /**
             * 双向链表的虚拟尾节点：尾节点为最近访问的节点
             */
            private Node tail;
            /**
             * 当前链表上的节点被访问的次数
             */
            private int freq;

            /**
             * 用于初始化虚拟节点
             */
            public DoublyLinkedList() {

            }

            public DoublyLinkedList(int freq) {
                head = new Node();
                tail = new Node();
                head.next = tail;
                tail.prev = head;
                this.freq = freq;
            }

            public void addNodeToHead(Node node) {
                node.prev = head;
                node.next = head.next;
                head.next.prev = node;
                head.next = node;
                node.linkedList = this;
            }

            public void removeNode(Node node) {
                node.prev.next = node.next;
                node.next.prev = node.prev;
            }
        }

        /**
         * 双向链表节点
         */
        static class Node {
            private int key;                        // 当前节点的键
            private int value;                      // 当前节点的值
            private DoublyLinkedList linkedList;    // 当前节点所属的链表

            private Node prev;      // 当前节点的前一个节点，越接近虚拟头节点，越近被访问
            private Node next;      // 当前节点的后一个节点，越接近虚拟尾节点，越久被访问

            public Node() {
            }

            public Node(int key, int value) {
                this.key = key;
                this.value = value;
            }
        }
    }

    public static void main(String[] args) {
        LFUCache cache = new LFUCache(2);
        cache.put(1, 1);
        cache.put(2, 2);
        System.out.println(cache.get(1));   // 1
        cache.put(3, 3);
        System.out.println(cache.get(2));   // -1
        System.out.println(cache.get(3));   // 3
        cache.put(4, 4);
        System.out.println(cache.get(1));   // -1
        System.out.println(cache.get(3));   // 3
        System.out.println(cache.get(4));   // 4
        System.out.println("////");

        LFUCache cache2 = new LFUCache(1);
        cache2.put(2, 1);
        System.out.println(cache2.get(1));
        System.out.println("////");

        LFUCache cache3 = new LFUCache(1);
        cache3.put(2, 1);
        System.out.println(cache3.get(2));
        cache3.put(3, 2);
        System.out.println(cache3.get(2));
        System.out.println(cache3.get(3));
    }
}